/**
 * Copyright 2009 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.sapien.action;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.ServletResponseAware;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.sapien.model.User;
import com.google.inject.Inject;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;

/**
 * Authenticate OpenID Action, controls the creation and login of users through openid.
 * @author Adam
 * @version $Id: AuthenticateOpenIDAction.java 30 2010-01-10 19:05:55Z a.ruggles $
 * 
 * Created on Apr 14, 2009 at 8:25:35 PM 
 */
@Results({
	@Result(name = "input", location = "openid.jsp"),
	@Result(name = "error", location = "openid.jsp")
})
public class AuthenticateOpenIDAction extends ActionSupport implements
		ServletRequestAware, ServletResponseAware, SessionAware {
	/**
     * The <code>Logger</code> is used by the application to generate a log messages.
     */
    private static final Logger LOG = LoggerFactory.getLogger(AuthenticateOpenIDAction.class);

	/**
	 * Serial Version UID.
	 */
	private static final long serialVersionUID = 5915553773473127275L;

	/**
	 * The openid4java consumer manager.
	 */
	private final ConsumerManager manager;

	/**
	 * User supplied OpenID identifier.
	 */
	private String openid; 

	/**
	 * A user record.
	 */
	private User user;

	/**
	 * The HttpServletRequest.
	 */
	private HttpServletRequest request;

	/**
	 * The HttpServletResponse.
	 */
	private HttpServletResponse response;

	/**
	 * The Session Map.
	 */
	private Map<String, Object> session;

	/**
	 * Set of attributes that have been returned.
	 */
	private Map<String, String> attributes = new HashMap<String, String>();

	/**
	 * Constructs a registration OpenID action.
	 * @param manager The openid consumer manager.
	 */
	@Inject
	public AuthenticateOpenIDAction(final ConsumerManager manager) {
		super();
		this.manager = manager;
	}

	/**
	 * Validates an open id for registration.
	 * {@inheritDoc}
	 * @see com.opensymphony.xwork2.ActionSupport#execute()
	 */
	@SkipValidation
	public String execute() throws Exception {
		LOG.debug("URL=[{}]\n QS=[{}]", request.getRequestURL().toString(),
				request.getQueryString().replaceAll("&", "\n"));
		// extract the parameters from the authentication response
	    // (which comes in as a HTTP request from the OpenID provider)
	    ParameterList openidResp = new ParameterList(request.getParameterMap());

	    // retrieve the previously stored discovery information
	    final DiscoveryInformation discovered = (DiscoveryInformation) session.get("discovered");

	    // extract the receiving URL from the HTTP request
	    final StringBuffer receivingURL = request.getRequestURL();
	    final String queryString = request.getQueryString();
	    if (queryString != null && queryString.length() > 0) {
	        receivingURL.append("?").append(request.getQueryString());
	    }

	    // verify the response
	    final VerificationResult verification = manager.verify(receivingURL.toString(), openidResp, discovered);

	    // examine the verification result and extract the verified identifier
	    Identifier verified = verification.getVerifiedId();
	    if (verified == null) {
	    	return ERROR;
	    }
	    user = new User();
	    AuthSuccess authSuccess = (AuthSuccess) verification.getAuthResponse();
	    if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
	    	LOG.info("Processed as OPENID_NS_AX");
	    	FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX);
	    	attributes.put("nickname", fetchResp.getAttributeValue("nickname"));
	    	attributes.put("email", fetchResp.getAttributeValue("email"));
	    	attributes.put("fullname", fetchResp.getAttributeValue("fullname"));
	    	attributes.put("first", fetchResp.getAttributeValue("first"));
	    	attributes.put("last", fetchResp.getAttributeValue("last"));
	    	attributes.put("language", fetchResp.getAttributeValue("language"));
	    	attributes.put("country", fetchResp.getAttributeValue("country"));
	    	for (Map.Entry<String, String> entry : attributes.entrySet()) {
	    		LOG.info("ATTR: " + entry.getKey() + " = " + entry.getValue());
	    	}
	    	user.setUsername(fetchResp.getAttributeValue("nickname"));
	    	user.setEmail(fetchResp.getAttributeValue("email"));
	    	user.setPassword(fetchResp.getAttributeValue("fullname"));
	    }
	    if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
	    	LOG.info("Processed as OPENID_NS_SREG");
            SRegResponse sregResp = (SRegResponse) authSuccess.getExtension(SRegMessage.OPENID_NS_SREG);
            if (StringUtils.isBlank(user.getEmail())) {
            	user.setEmail(sregResp.getAttributeValue("email"));
            	attributes.put("email", sregResp.getAttributeValue("email"));
            }
            if (StringUtils.isBlank(user.getPassword())) {
            	user.setPassword(sregResp.getAttributeValue("fullname"));
            	attributes.put("fullname", sregResp.getAttributeValue("fullname"));
            }
            if (StringUtils.isBlank(user.getUsername())) {
            	user.setUsername(sregResp.getAttributeValue("nickname"));
            	attributes.put("nickname", sregResp.getAttributeValue("nickname"));
            }
        }

	    openid = verified.getIdentifier();
		return SUCCESS;
	}
	/**
	 * Returns openid.
	 * @return the openid.
	 */
	@RequiredStringValidator(message = "Required Field.", key = "validation.required")
	public String getOpenid() {
		return openid;
	}

	/**
	 * Returns the full server context.
	 * @param request The Http Servlet Request.
	 * @return The requested URL.
	 */
	private String getServerContext(final HttpServletRequest request) {
		// Get the base url.
        final StringBuilder serverPath = new StringBuilder();
        serverPath.append(request.getScheme() + "://");
        serverPath.append(request.getServerName());
        if (request.getServerPort() != 80) {
            serverPath.append(":" + request.getServerPort());
        }
        serverPath.append(request.getContextPath());
        return serverPath.toString();
	}

	/**
	 * Sets openid.
	 * @param openid the openid to set.
	 */
	public void setOpenid(final String openid) {
		this.openid = openid;
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.struts2.interceptor.ServletRequestAware#setServletRequest(
	 * javax.servlet.http.HttpServletRequest)
	 */
	public void setServletRequest(final HttpServletRequest request) {
		this.request = request;
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.struts2.interceptor.ServletResponseAware#setServletResponse(
	 * javax.servlet.http.HttpServletResponse)
	 */
	public void setServletResponse(final HttpServletResponse response) {
		this.response = response;
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.struts2.interceptor.SessionAware#setSession(java.util.Map)
	 */
	public void setSession(final Map<String, Object> session) {
		this.session = session;
	}

	/**
	 * Handles the initial request to register by the OpenID.
	 * @return NONE.
	 * @throws Exception If an error occurs processing the request.
	 */
	@SuppressWarnings("unchecked")
	@Action("validate-openid")
	public String validateOpenid() throws Exception {
		if (openid.endsWith("/")) {
			LOG.debug("OPEN ID BEFORE {}", openid);
			openid = openid.substring(0, openid.length() - 1);
			LOG.debug("OPEN ID AFTER {}", openid);
		}
		LOG.debug("your raw openid request is {}", openid);
		LOG.debug("your openid request is being sent {}", StringEscapeUtils.unescapeHtml(openid));
		String returnUrl = getServerContext(request) + "/authenticate-open-id";
		List<DiscoveryInformation> discoveries = manager.discover(openid);
		// attempt to associate with the OpenID provider
	    // and retrieve one service endpoint for authentication
		DiscoveryInformation discovered = manager.associate(discoveries);
		// store the discovery information in the user's session for later use
	    // leave out for stateless operation / if there is no session
		session.put("discovered", discovered);
		// obtain a AuthRequest message to be sent to the OpenID provider
		AuthRequest authReq = manager.authenticate(discovered, returnUrl);
		// Attribute Exchange example: fetching the 'email' attribute
		FetchRequest fetch = FetchRequest.createFetchRequest();
        fetch.addAttribute("first", "http://axschema.org/namePerson/first", true);
        fetch.addAttribute("last", "http://axschema.org/namePerson/last", true);
        fetch.addAttribute("fullname", "http://axschema.org/namePerson", true);
        fetch.addAttribute("nickname", "http://axschema.org/namePerson/friendly", true);
        fetch.addAttribute("email", "http://axschema.org/contact/email", true);
        fetch.addAttribute("language", "http://axschema.org/pref/language", true);
        fetch.addAttribute("country", "http://axschema.org/contact/country/home", true);
        // attach the extension to the authentication request
        authReq.addExtension(fetch);
        /*
        SRegRequest sregReq = SRegRequest.createFetchRequest();
        sregReq.addAttribute("email", true);
        sregReq.addAttribute("nickname", true);
        sregReq.addAttribute("fullname", true);
        authReq.addExtension(sregReq);
        */
        LOG.info("The request string is:\n{}", authReq.getDestinationUrl(true).replaceAll("&", "\n"));
		response.sendRedirect(authReq.getDestinationUrl(true));
		return NONE;
	}

	/**
	 * Returns request.
	 * TODO remove this method!
	 * @return the request.
	 */
	public HttpServletRequest getRequest() {
		return request;
	}

	/**
	 * Returns user.
	 * @return the user.
	 */
	public User getUser() {
		return user;
	}

}
